package erfgame.core.path;

import java.awt.Point;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;


import erfgame.core.dao.ObjectDAO;
import erfgame.core.entity.Entity;
import erfgame.core.world.World;
import erfgame.core.world.WorldContext;
import erfgame.core.world.WorldDAOManager;
import erfgame.core.world.terrain.BlockGridTiles;
import erfgame.core.world.terrain.GridTile;
import erfgame.core.world.terrain.TerrainUtils;
import erfgame.core.world.terrain.WalkablePath;

public class BreadthFirstPathCalculator implements PathCalculator {
	
	private WorldDAOManager worldDAOManager; 
	private WorldContext worldContext;
	private Coster coster;
	
	public BreadthFirstPathCalculator( WorldDAOManager worldDAOManager, WorldContext worldContext, Coster coster ) {
		this.worldContext = worldContext;
		this.worldDAOManager = worldDAOManager;
		this.coster = coster;
	}
	
	public List<PathElement> calculatePath( 
			Entity client,
			int gxFrom, int gyFrom, int gIndexFrom, 
			int gxTo, int gyTo, int gIndexTo,
			int bx, int by, int bw, int bh ) {
		World world = worldContext.getCurrentWorld();
		PathCost[][][] costs = calculateCosts( client, world, gxFrom, gyFrom, gIndexFrom, bx, by, bw, bh );
		int gridsX = world.getXGridTilesPerBlock();
		int gridsY = world.getYGridTilesPerBlock();
		int mingx = bx * gridsX;
		int mingy = by * gridsY;
		
		int gx = gxTo;
		int gy = gyTo;
		LinkedList<PathElement> result;
		PathCost[] gCosts = costs[gx - mingx][gy - mingy]; 
		if( gCosts == null || gCosts[gIndexTo] == null ) {
			result = null;
		} else {
			result = new LinkedList<PathElement>();	
			Point coords = new Point();
			while( gx != gxFrom || gy != gyFrom ) {
//				System.out.println( "("+gx+","+gy+")" );
				PathCost cost = costs[gx - mingx][gy - mingy][gIndexTo];
				int direction = cost.pathDirection;
//				System.out.println( direction +"->("+cost.pathX+","+cost.pathY+")" );
				PathElement element = new PathElement(
						cost.pathX, 
						cost.pathY, 
						cost.pathIndex, 
						direction >= 2
				);
				result.add( 0, element );
				TerrainUtils.getGridCoordinates(gx, gy, (direction+2)%4, coords);
				gx = coords.x;
				gy = coords.y; 
			}
		}
		return result;
	}
	
	public PathCost[][][] calculateCosts( Entity client, World world, int gx, int gy, int gIndex, int bx, int by, int bw, int bh ) {
		int gridsX = world.getXGridTilesPerBlock();
		int gridsY = world.getYGridTilesPerBlock();
		PathCost[][][] costs = new PathCost[gridsX * bw][gridsY * bh][];
		
		int mingx = bx * gridsX;
		int mingy = by * gridsY;
		int maxgx = mingx + bw * gridsX - 1;
		int maxgy = mingy + bh * gridsY - 1;
		calculateCosts( 
				client,
				world,
				gx, gy, gIndex,
				mingx, mingy, maxgx, maxgy, 
				costs, 
				1
		);
		
		return costs;
	}
	
	public void calculateCosts( 
			Entity client,
			World world,
			int startgx, int startgy, int startgIndex, 
			int mingx, int mingy, int maxgx, int maxgy, 
			PathCost[][][] costs, 
			int startingCost
	) {
		ObjectDAO<Point, BlockGridTiles> gridTilesDAO = worldDAOManager.getGridTilesDAO(world);
		ArrayList<WaitingNode> points = new ArrayList<WaitingNode>();
		points.add( new WaitingNode( startgx, startgy, startgIndex, startingCost, startgx, startgy, 0, 0 ) );
		while( !points.isEmpty() ) { 
			WaitingNode p = points.remove( 0 );
			int gx = p.gx;
			int gy = p.gy;
			int gIndex = p.gIndex;
			if( gIndex < 0 ) {
				System.out.println( "negative index at ("+gx+","+gy+")");
				continue;
			}
			int currentCost = p.cost;
			int bx = gx / world.getXGridTilesPerBlock();
			int by = gy / world.getYGridTilesPerBlock();
			int blockgx = gx % world.getXGridTilesPerBlock();
			int blockgy = gy % world.getYGridTilesPerBlock();
			int localgx = gx - mingx;
			int localgy = gy - mingy;
			Point b = new Point( bx, by );
			BlockGridTiles gridTiles = gridTilesDAO.retrieveObject(b);
			PathCost[] gcosts = costs[ localgx ][ localgy ];
			PathCost foundCost;
			if( gcosts == null ) {
				gcosts = new PathCost[ gridTiles.getGridTiles(blockgx, blockgy).length ];
				costs[localgx][localgy] = gcosts;
				foundCost = null;
			} else {
				foundCost = gcosts[gIndex]; 
			}
//			System.out.println( "("+gx+","+gy+"="+foundCost+"?"+currentCost+")" );
			if( foundCost == null || foundCost.cost > currentCost ) {
				//System.out.println( foundCost+">"+currentCost );
				PathCost cost = new PathCost( 
						currentCost, 
						p.fromPathX, 
						p.fromPathY,
						p.fromPathDirection, 
						p.fromPathIndex 
				);
				gcosts[gIndex] = cost;
//				System.out.println( "["+localgx+","+localgy+","+gIndex+"]="+cost );
				// recalculate the links
				GridTile gridTile = gridTiles.getGridTile(blockgx, blockgy, gIndex);
				int[][] indexes = gridTile.getPathIndexes();
				Point pathCoordinates = new Point();
				Point gridCoordinates = new Point();
				for( int direction = 4; direction>0;  ) {
					direction--;
					TerrainUtils.getGridCoordinates(gx, gy, direction, gridCoordinates);
					int nextGx = gridCoordinates.x;
					int nextGy = gridCoordinates.y;
					if( 
							nextGx >= mingx && 
							nextGx <= maxgx && 
							nextGy >= mingy && 
							nextGy <= maxgy 
					)
					{
//						System.out.println( "->("+nextGx+","+nextGy+")" );
						int[] pathIndexes = indexes[ direction ];
						if( pathIndexes == null ) {
							System.out.println("no index at "+direction+" ("+nextGx+","+nextGy+")!!");
							continue;
						}

						TerrainUtils.getPathCoordinates(gx, gy, direction, pathCoordinates);
						int nextBlockPx = pathCoordinates.x;
						int nextBlockPy = pathCoordinates.y;
//						System.out.println( "("+gx+","+gy+") => ("+nextBlockPx+","+nextBlockPy+")" );
						int pathBlockX = nextBlockPx / world.getXPathTilesPerBlock();
						int pathBlockY = nextBlockPy / world.getYPathTilesPerBlock();
						BlockGridTiles pathTiles;
						if( pathBlockX != bx || pathBlockY != by ) {
							Point pb = new Point( pathBlockX, pathBlockY );
							pathTiles = gridTilesDAO.retrieveObject(pb );
						} else {
							pathTiles = gridTiles; 
						}
						int px = nextBlockPx % world.getXPathTilesPerBlock();
						int py = nextBlockPy % world.getYPathTilesPerBlock();
						
						WalkablePath[] paths = pathTiles.getPaths(px, py);
						for( int i=pathIndexes.length; i>0; ) {
							i--;
							int pathIndex = pathIndexes[ i ];
							if( pathIndex >= paths.length ) {
								System.out.println( "mismatched indexes "+direction +" ("+nextBlockPx+","+nextBlockPy+")" );
							}
							WalkablePath path = paths[ pathIndex ];
							int nextGridIndex;
							if( direction < 2 ) {
								nextGridIndex = path.getToGridIndex();
							} else {
								nextGridIndex = path.getFromGridIndex();
							}
							int pathCost = coster.getCost(client, nextBlockPx, nextBlockPy, pathIndex, path);
							if( pathCost > 0 ) {
//							calculateCosts( 
//									nextGx, nextGy, nextGridIndex, 
//									mingx, mingy, maxgx, maxgy, 
//									costs,
//									currentCost + pathCost
//							);
								points.add( 
										new WaitingNode( 
												nextGx, nextGy, 
												nextGridIndex,
												currentCost + pathCost, 
												nextBlockPx, nextBlockPy,
												direction, i
										) 
								);
							}
						}
					}				
				}
			}
		}
	}
	
	private static class WaitingNode {
		private int gx; 
		private int gy;
		private int gIndex;
		private int cost;
		private int fromPathX;
		private int fromPathY;
		private int fromPathDirection;
		private int fromPathIndex;
		
		public WaitingNode( int gx, int gy, int gIndex, int cost, int fromPathX, int fromPathY, int fromPathDirection, int fromPathIndex ) {
			this.gx = gx;
			this.gy = gy;
			this.gIndex = gIndex;
			this.cost = cost;
			this.fromPathX = fromPathX;
			this.fromPathY = fromPathY;
			this.fromPathDirection = fromPathDirection;
			this.fromPathIndex = fromPathIndex;
		}
		
		public String toString() {
			return "(x="+gx+",y="+gy+",index="+gIndex+",cost="+cost+",px="+fromPathX+",py="+fromPathY+")";
		}
	}
	
	private static final class PathCost {
		private int cost;
		private int pathIndex;
		private int pathDirection;
		private int pathX;
		private int pathY;
		
		public PathCost( int cost, int pathX, int pathY, int pathDirection, int pathIndex ) {
			this.cost = cost;
			this.pathX = pathX;
			this.pathY = pathY;
			this.pathIndex = pathIndex;
			this.pathDirection = pathDirection;
		}
		
		public String toString() {
			return "(x="+pathX+",y="+pathY+",c="+cost+",d="+pathDirection+",i="+pathIndex+")";
		}
	}
}
